const SunTools = function () {

}
SunTools.prototype = {
  /******************************************/
  /************** 相关转换工具 ***************/
  /******************************************/
  /**
   *
   * @param {Number} hour
   * @param {Number} mim
   * @param {Number} sec
   * @param {Boolean} pm
   * @param {Boolean} dst
   * @returns 返回当前时间过了多少分钟(0点开始计时)
   */
  getLocalTime: function (hour, mim, sec, pm = false, dst = false) {
    // if (pm && hour < 12) dochr += 12
    // if (dst) dochr -= 1
    return hour * 60 + mim + sec / 60.0
  },

// 判断是否是闰年
  isLeapYear: function (year) {
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
  },

// 弧度制转角度制
  radToDeg: function (angleRad) {
    return (180.0 * angleRad) / Math.PI
  },

// 角度制转弧度制
  degToRad: function (angleDeg) {
    return (Math.PI * angleDeg) / 180.0
  },

// 根据<年,月,日>获取儒略日
  getJulianDay: function (year, month, day) {
    let A = Math.floor(year / 100)
    let B = 2 - A + Math.floor(A / 4)
    let JD = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5
    return JD
  },

  /**
   * @param {Number} JulianDay => 儒略日
   * 儒略日的起始为公元前 4713 年 1 月 1 日中午 12 点
   * 每过一“平太阳日”加一 => 即通常意义上的“一天”
   * 地球在轨道上做的是不等速运动,一年之内真太阳日的长度不断改变,于是引进平太阳的概念
   * 一年中<真太阳日>的平均即为<平太阳日>
   * 2000 年 1 月 1 日的 UT12:00 是儒略日 2451545
   * @returns 儒略日至 2000 年始经过的世纪数(一儒略年有 365.25 天)
   */
  calcTimeJulianCent: function (JulianDay) {
    return (JulianDay - 2451545.0) / 36525.0
  },

// 将 d 控制在 0~Range 内
  correctRange: function (value, Range) {
    value = value % Range
    if (value < 0) {
      value += Range
    }
    return value
  },

  /**
   * 将时间(以分钟为单位)格式化
   * @param {Number} minutes
   * @param {Number} flag
   * flag = 1 格式化成 HH
   * flag = 2 格式化成 HH:MM
   * flag = 3 格式化成 HH:MM:SS
   */
  timeString: function (minutes, flag) {
    let output
    if (minutes >= 0 && minutes < 1440) {
      let floatHour = minutes / 60.0
      let hour = Math.floor(floatHour)

      let floatMinute = 60.0 * (floatHour - Math.floor(floatHour))
      let minute = Math.floor(floatMinute)

      let floatSec = 60.0 * (floatMinute - Math.floor(floatMinute))
      let second = Math.floor(floatSec + 0.5) // 大于 59.5s 就向上取整

      if (second > 59) {
        // second>59 只会是 60s
        second = 0
        minute += 1
      }
      // 需要格式化成 HH:MM 时
      if (flag === 2 && second >= 30) minute++ // 当超过 30s 时分钟数进 1
      if (minute > 59) {
        minute = 0
        hour += 1
      }
      output = this.zeroPad(hour, 2) + ':' + this.zeroPad(minute, 2)
      if (flag > 2) output = output + ':' + this.zeroPad(second, 2)
    } else {
      let output = 'error'
    }
    return output
  },

// 不足补 0
  zeroPad: function (n, digits) {
    n = String(n)
    while (n.length < digits) {
      n = '0' + n
    }
    return n
  },

  /******************************************/
  /********** 太阳坐标计算开始 ***************/
  /******************************************/

  /***
   * @param {Number} t => Julian century(经过的世纪数)
   * 计算太阳的平黄经(geometric mean longitude [L0])
   * 黄经：天球黄道坐标系中的经度 => 用来确定天体在天球上的位置
   */
  calcGeomMeanLongSun: function (t) {
    let L0 = 280.46646 + 36000.76983 * t + 0.0003032 * Math.pow(t, 2)
    return this.correctRange(L0, 360)
  },

  /**
   * 计算太阳的平近点角(mean anomaly [M])
   * 平近点角：轨道上的物体在辅助圆上相对于中心点的运行角度
   * 参考：https://en.wikipedia.org/wiki/Mean_anomaly
   */
  calcGeomMeanAnomalySun: function (t) {
    let M = 357.52911 + 35999.05029 * t - 0.0001537 * Math.pow(t, 2)
    return this.correctRange(M, 360)
  },

  /**
   * 计算地球轨道偏心率(eccentricity [e])
   * 数学上称为“离心率”
   * 对于椭圆 => 两焦点间的距离(2c)和长轴长度(2a)的比值,即 e=c/a
   */
  calcEccentricityEarthOrbit: function (t) {
    return 0.016708634 - 0.000042037 * t + 0.0000001267 * Math.pow(t, 2)
  },

  /**
   * 圆心方程(Equation of the Center)
   * 计算角差(椭圆轨道上实际位置与它在同一周期的圆形轨道上匀速运动时所占位置之间的角差)
   * 参考 https://en.wikipedia.org/wiki/Equation_of_the_center
   */
  calcSunEqOfCenter: function (t) {
    let M = this.calcGeomMeanAnomalySun(t) // 太阳平近点角
    let mrad = this.degToRad(M)
    let sinm = Math.sin(mrad)
    let sin2m = Math.sin(2 * mrad)
    let sin3m = Math.sin(3 * mrad)
    return sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289 // 单位为度
  },

// 计算太阳真实经度(True Longitude)
  calcSunTrueLong: function (t) {
    let l0 = this.calcGeomMeanLongSun(t) // 太阳平黄经
    let C = this.calcSunEqOfCenter(t) // 中心方程
    return this.correctRange(l0 + C, 360) // 单位为度
  },

// 计算太阳真实平近点角(True Anomaly)
  calcSunTrueAnomaly: function (t) {
    let M = this.calcGeomMeanAnomalySun(t)
    let C = this.calcSunEqOfCenter(t)
    return this.correctRange(M + C, 360) // ν(nu)单位为度
  },

  /**
   * 计算半径矢量 => 太阳中心到地球中心的距离
   */
  calcSunRadVector: function (t) {
    let n = this.calcSunTrueAnomaly(t)
    let e = this.calcEccentricityEarthOrbit(t)
    return (1.000001018 * (1 - Math.pow(e, 2))) / (1 + e * Math.cos(degToRad(n)))
  },

  /**
   * 计算太阳表观经度
   * 参考 https://en.wikipedia.org/wiki/Apparent_longitude
   */
  calcSunApparentLong: function (t) {
    let Ltrue = this.calcSunTrueLong(t)
    let omega = 125.04 - 1934.136 * t
    return Ltrue - 0.00569 - 0.00478 * Math.sin(this.degToRad(omega)) // 单位为度
  },

  /**
   * 计算黄赤交角
   */
  calcMeanObliquityOfEcliptic: function (t) {
    let seconds = 21.448 - t * (46.815 + t * (0.00059 - t * 0.001813))
    return 23.0 + (26.0 + seconds / 60.0) / 60.0 // 单位为度
  },

  /**
   * 计算太阳坐标
   */
  calcObliquityCorrection: function (t) {
    let e0 = this.calcMeanObliquityOfEcliptic(t)
    let omega = 125.04 - 1934.136 * t
    return e0 + 0.00256 * Math.cos(this.degToRad(omega)) // 单位为度
  },

// 计算太阳赤经
  calcSunRtAscension: function (t) {
    let e = this.calcObliquityCorrection(t)
    let Lapp = this.calcSunApparentLong(t)
    let tananum = Math.cos(this.degToRad(e)) * Math.sin(this.degToRad(Lapp))
    let tanadenom = Math.cos(this.degToRad(Lapp))
    let alpha = this.radToDeg(Math.atan2(tananum, tanadenom))
    return alpha.toFixed(2) // 单位为度
  },

// 计算太阳赤纬
  calcSunDeclination: function (t) {
    let e = this.calcObliquityCorrection(t)
    let Lapp = this.calcSunApparentLong(t)
    let sint = Math.sin(this.degToRad(e)) * Math.sin(this.degToRad(Lapp))
    let theta = this.radToDeg(Math.asin(sint))
    return theta.toFixed(2) // 单位为度
  },

  /**
   * 计算真太阳日与平太阳日之差(即“时差”)
   * 与当前是几点无关 => 即计算的是 00:00:00 的时差
   */
  calcEquationOfTime: function (t) {
    let epsilon = this.calcObliquityCorrection(t)
    let l0 = this.calcGeomMeanLongSun(t)
    let e = this.calcEccentricityEarthOrbit(t)
    let m = this.calcGeomMeanAnomalySun(t)

    let y = Math.tan(this.degToRad(epsilon) / 2.0)
    y *= y

    let sin2l0 = Math.sin(2.0 * this.degToRad(l0))
    let sinm = Math.sin(this.degToRad(m))
    let cos2l0 = Math.cos(2.0 * this.degToRad(l0))
    let sin4l0 = Math.sin(4.0 * this.degToRad(l0))
    let sin2m = Math.sin(2.0 * this.degToRad(m))

    let Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m
    return Math.floor(this.radToDeg(Etime) * 4.0 * 100 + 0.5) / 100.0 // 单位为分钟
  },

  /**
   * 根据当前时间对儒略日进行修正
   */
  CorrectJD: function (t, hour, mim, sec, tz) {
    //  获取分钟数
    let mims = this.getLocalTime(hour, mim, sec)
    // 获取时间修正的儒略日(+小时数-时区差)
    return t + mims / 1440.0 - tz / 24.0
  },

// 计算日出角
  calcHourAngleSunrise: function (lat, solarDec) {
    let latRad = this.degToRad(lat)
    let sdRad = this.degToRad(solarDec)
    let HAarg = Math.cos(degToRad(90.833)) / (Math.cos(latRad) * Math.cos(sdRad)) - Math.tan(latRad) * Math.tan(sdRad)
    return Math.acos(HAarg) // 单位弧度
  },

// 计算日落角
  calcHourAngleSunset: function (lat, solarDec) {
    return -this.calcHourAngleSunrise(lat, solarDec) // 单位弧度
  },

  /******************************************/
  /********** 太阳坐标计算结束 ***************/
  /******************************************/

  /**
   * 计算地球上给定位置、给定日期“太阳正午”的世界协调时间(UTC)
   * 太阳高度角最大的时候
   * @return HH:MM:SS 格式的正午时间
   */
  calcSolNoon: function (julianday, longitude, timezone, dst) {
    let tnoon = this.calcTimeJulianCent(julianday - longitude / 360.0)
    let eqTime = this.calcEquationOfTime(tnoon)
    let solNoonOffset = 720.0 - longitude * 4 - eqTime // 单位为分钟
    let newt = this.calcTimeJulianCent(jd + solNoonOffset / 1440.0)
    eqTime = this.calcEquationOfTime(newt)
    let solNoonLocal = 720 - longitude * 4 - eqTime + timezone * 60.0 // 单位为分钟
    if (dst) solNoonLocal += 60.0 // 如果采用夏时令则加 60 分钟(提前 1 小时)
    // 将时间控制在 24 小时(1440 分钟) 内
    solNoonLocal = this.correctRange(solNoonLocal, 1440.0)
    return this.timeString(solNoonLocal, 3)
  },

  /**
   * @param {Number} rise rise = 1 计算日出时间, 0 计算日落时间
   * @param {Number} JD 儒略日
   * @param {Number} latitude 纬度
   * @param {Number} longitude 经度
   * 返回日出日落时间的分钟数
   */
  calcSunriseSetUTC: function (rise, JD, latitude, longitude) {
    let t = this.calcTimeJulianCent(JD)
    let eqTime = this.calcEquationOfTime(t)
    let solarDec = this.calcSunDeclination(t)
    let hourAngle = this.calcHourAngleSunrise(latitude, solarDec)
    if (!rise) hourAngle = -hourAngle
    let delta = longitude + this.radToDeg(hourAngle)
    // 单位为分钟
    return 720 - 4.0 * delta - eqTime
  },

// 计算日出日落时间
  calcSunriseSet: function (rise, JD, latitude, longitude, timezone, dst) {
    let timeUTC = this.calcSunriseSetUTC(rise, JD, latitude, longitude)
    let newTimeUTC = this.calcSunriseSetUTC(rise, JD + timeUTC / 1440.0, latitude, longitude)
    // 时区修正的日出日落分钟数
    let timeLocal = newTimeUTC + timezone * 60.0
    // 夏时令修正
    timeLocal += dst ? 60.0 : 0.0
    // 控制 timeLocal 范围在 0~1440.0 内
    timeLocal = this.correctRange(timeLocal, 1440.0)
    return this.timeString(timeLocal, 2)
  },

  /**
   * @param t
   * @param {Number} localtime => 通过 getLocalTime 获得的时间
   * 计算太阳高度角
   * @param latitude
   * @param longitude
   * @param zone
   */
  calcelevation: function (t, localtime, latitude, longitude, zone) {
    let eqTime = this.calcEquationOfTime(t) // 时差
    let theta = this.calcSunDeclination(t) // 赤纬
    let solarTimeFix = eqTime + 4.0 * longitude - 60.0 * zone

    let trueSolarTime = localtime + solarTimeFix

    trueSolarTime = this.correctRange(trueSolarTime, 1440)

    let hourAngle = trueSolarTime / 4.0 - 180.0
    if (hourAngle < -180) hourAngle += 360.0

    let haRad = this.degToRad(hourAngle)
    let csz = Math.sin(this.degToRad(latitude)) * Math.sin(this.degToRad(theta)) + Math.cos(this.degToRad(latitude)) * Math.cos(this.degToRad(theta)) * Math.cos(haRad)
    if (csz > 1.0) {
      csz = 1.0
    } else if (csz < -1.0) {
      csz = -1.0
    }
    let zenith = this.radToDeg(Math.acos(csz))

    let exoatmElevation = 90.0 - zenith

    let refractionCorrection
    if (exoatmElevation > 85.0) {
      refractionCorrection = 0.0
    } else {
      let te = Math.tan(this.degToRad(exoatmElevation))
      if (exoatmElevation > 5.0) {
        refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te)
      } else if (exoatmElevation > -0.575) {
        refractionCorrection = 1735.0 + exoatmElevation * (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711)))
      } else {
        refractionCorrection = -20.774 / te
      }
      refractionCorrection = refractionCorrection / 3600.0
    }

    let solarZen = zenith - refractionCorrection

    // 太阳高度角
    return Math.floor((90.0 - solarZen) * 100 + 0.5) / 100.0
  },

  /**
   * @param t
   * @param localtime
   * @param latitude
   * @param longitude
   * @param zone
   * @param {Number} ZeroAzimuth 零方位角
   * 零方位角 = 北, ZeroAzimuth=0
   * 零方位角 = 南, ZeroAzimuth=180
   */
  calcazimuth: function (t, localtime, latitude, longitude, zone, ZeroAzimuth) {
    let eqTime = this.calcEquationOfTime(t) // 时差
    let theta = this.calcSunDeclination(t) // 赤纬
    let solarTimeFix = eqTime + 4.0 * longitude - 60.0 * zone

    let trueSolarTime = localtime + solarTimeFix

    trueSolarTime = this.correctRange(trueSolarTime, 1440)

    let hourAngle = trueSolarTime / 4.0 - 180.0
    if (hourAngle < -180) hourAngle += 360.0

    let haRad = this.degToRad(hourAngle)
    let csz = Math.sin(this.degToRad(latitude)) * Math.sin(this.degToRad(theta)) + Math.cos(this.degToRad(latitude)) * Math.cos(this.degToRad(theta)) * Math.cos(haRad)
    if (csz > 1.0) {
      csz = 1.0
    } else if (csz < -1.0) {
      csz = -1.0
    }
    let zenith = this.radToDeg(Math.acos(csz))
    let azDenom = Math.cos(this.degToRad(latitude)) * Math.sin(this.degToRad(zenith))
    let azimuth
    if (Math.abs(azDenom) > 0.001) {
      let azRad = (Math.sin(this.degToRad(latitude)) * Math.cos(this.degToRad(zenith)) - Math.sin(this.degToRad(theta))) / azDenom
      if (Math.abs(azRad) > 1.0) {
        if (azRad < 0) {
          azRad = -1.0
        } else {
          azRad = 1.0
        }
      }
      azimuth = 180.0 - this.radToDeg(Math.acos(azRad))
      if (hourAngle > 0.0) {
        azimuth = -azimuth
      }
    } else {
      if (latitude > 0.0) {
        azimuth = 180.0
      } else {
        azimuth = 0.0
      }
    }
    if (azimuth < 0.0) {
      azimuth += 360.0
    }
    return (Math.floor(azimuth * 100 + 0.5) - ZeroAzimuth * 100) / 100.0
  }
}

export {
  SunTools
}